using System;
using System.Collections.Generic;
using System.Data;
using System.Globalization;
using System.IO;
using System.Text;

namespace Tyng.Data
{

    /// <summary>
    /// Abstract class representing Sql Script writer
    /// </summary>
    /// <remarks>
    /// Concrete classes will implement server specific syntax for writing specific SQL constructs
    /// </remarks>
    public abstract class SqlScriptWriter : TextWriter
    {
        TextWriter _tw;

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="tw">TextWriter to proxy to</param>
        protected SqlScriptWriter(TextWriter tw) : base(CultureInfo.InvariantCulture)
        {
            if (tw == null) throw new ArgumentNullException("tw");

            _tw = tw;
        }

        #region Proxy Overrides

        /// <summary>
        /// Encoding of the TextWriter
        /// </summary>
        public override Encoding Encoding
        {
            get { return _tw.Encoding; }
        }

        /// <summary>
        /// Proxy Write override
        /// </summary>
        /// <param name="value">Character to write</param>
        public override void Write(char value)
        {
            EnsureBatch();

            _tw.Write(value);
        }

        #endregion

        #region Batching
        bool _inBatch = false;

        /// <summary>
        /// Begin a SQL statement batch.
        /// </summary>
        /// <remarks>
        /// This will end any previously started SQL batches and also declare any variables present.
        /// </remarks>
        public void BeginBatch()
        {
            if (_inBatch)
                EndBatch();

            BeginBatchInternal();
            _inBatch = true;

            RenderVariableDeclarations();
        }

        /// <summary>
        /// Virtual method to write any batch beginning specific syntax
        /// </summary>
        protected abstract void BeginBatchInternal();

        /// <summary>
        /// End a SQL batch
        /// </summary>
        /// <remarks>
        /// If any variables are present, they will be undefined before ending the batch.
        /// </remarks>
        public void EndBatch()
        {
            if (!_inBatch)
                throw new InvalidOperationException();

            EndBatchInternal();
            _inBatch = false;
        }

        /// <summary>
        /// Ensure that the calling statement is in a batch.
        /// </summary>
        protected void EnsureBatch()
        {
            if (!IsInBatch)
                BeginBatch();
        }

        /// <summary>
        /// Flag indicating TextWriter is currently in the middle of a batch.
        /// </summary>
        public bool IsInBatch { get { return _inBatch; } }

        /// <summary>
        /// Virtual method to add any server specific batch ending syntax (ie. "GO" in SQL Server)
        /// </summary>
        protected abstract void EndBatchInternal();
        #endregion

        #region Variables

        List<Variable> _declared = new List<Variable>();
        List<Variable> _variables = new List<Variable>();

        /// <summary>
        /// Add a variable for this batch
        /// </summary>
        /// <param name="name">Name of the variable (without any server specific prefix)</param>
        /// <param name="type">Server independent type of the paramter</param>
        public void AddVariable(string name, DbType type)
        {
            _variables.Add(new Variable(name, type));
        }

        /// <summary>
        /// Render declarations of undeclared variables
        /// </summary>
        public void RenderVariableDeclarations()
        {
            List<Variable> toDeclare = new List<Variable>(_variables);

            foreach (Variable v in _variables)
            {
                if (_declared.Contains(v))
                    toDeclare.Remove(v);
            }

            RenderVariableDeclarationsInternal(toDeclare);

            _declared.AddRange(toDeclare);
        }

        /// <summary>
        /// Abstract method for server specific implementations to render variable declarations
        /// </summary>
        /// <param name="variables">Variables to declare</param>
        protected abstract void RenderVariableDeclarationsInternal(List<Variable> variables);

        /// <summary>
        /// Class representing a variable
        /// </summary>
        protected sealed class Variable
        {
            /// <summary>
            /// Name of the variable
            /// </summary>
            public string Name;
            /// <summary>
            /// Type of the variable
            /// </summary>
            public DbType DbType;
            /// <summary>
            /// Variable is nullable
            /// </summary>
            public bool IsNullable = false;
            /// <summary>
            /// Size of the variable
            /// </summary>
            public int Size;
            /// <summary>
            /// Constructor
            /// </summary>
            /// <param name="name"></param>
            /// <param name="dbType"></param>
            public Variable(string name, DbType dbType) { this.Name = name; this.DbType = dbType; }
        }

        #endregion
    }
}
